﻿using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;

namespace UnitTesting
{
    public class ActivityInjector
    {
        private readonly Type myActivityType;

        private readonly object myActivty;

        private readonly string myChildName;

        private readonly List<object> myActivityImplementations;

        private bool myIsUsingActivityImplementation = false;

        private bool myIsUsingCollection = false;

        public ActivityInjector(Type activityType, string childName)
        {
            this.myActivityType = activityType;
            this.myChildName = childName;
            this.myActivty = this.CreateObject(this.myActivityType);

            var activityImplementation = this.GetFieldValue(this.myChildName, this.myActivty);
            if (activityImplementation == null)
            {
                throw new ArgumentException("Activity implementation is not found.");
            }

            this.myActivityImplementations = new List<object> { activityImplementation };
        }

        public ActivityInjector(object activityObject, string childName)
        {
            if (activityObject == null)
            {
                throw new ArgumentException("Null activity passed.");
            }

            this.myActivty = activityObject;
            this.myActivityType = this.myActivty.GetType();
            this.myChildName = childName;

            var activityImplementation = this.GetFieldValue(this.myChildName, this.myActivty);
            if (activityImplementation == null)
            {
                throw new ArgumentException("Activity implementation is not found.");
            }

            this.myActivityImplementations = new List<object> { activityImplementation };
        }

        public ActivityInjector(Type activityType)
        {
            this.myActivityType = activityType;
            this.myActivty = this.CreateObject(this.myActivityType);
            this.myActivityImplementations = this.FindImplementationFields(this.myActivty);

            if (this.myActivityImplementations == null || this.myActivityImplementations.Count == 0)
            {
                throw new ArgumentException("Activity implementation is not found.");
            }
        }

        public ActivityInjector(object activityObject)
        {
            if (activityObject == null)
            {
                throw new ArgumentException("Null activity passed.");
            }
            this.myActivty = activityObject;
            this.myActivityType = this.myActivty.GetType();
            this.myActivityImplementations = this.FindImplementationFields(this.myActivty);

            if (this.myActivityImplementations == null || this.myActivityImplementations.Count == 0)
            {
                throw new ArgumentException("Activity implementation is not found.");
            }
        }

        private object CreateObject(Type objectType)
        {
            return Activator.CreateInstance(objectType);
        }

        private object GetFieldValue(string fieldName, object srcObject)
        {
            var fieldInfo = srcObject.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
            if (fieldInfo == null)
            {
                throw new Exception("Implementation Child Name is incorrect");
            }

            return fieldInfo.GetValue(srcObject);
        }

        public void Replace(Type srcActivityType, Type targetActivityType)
        {
            var foundActivity = this.myActivityImplementations.Find(x => x.GetType() == srcActivityType) as Activity;
            if (foundActivity != null)
            {
                this.ReplaceInImplementationList(null, srcActivityType, targetActivityType);
            }

            foreach (var activityImplementation in this.myActivityImplementations)
            {
                this.FindAndReplace(activityImplementation, null, srcActivityType, targetActivityType);
            }
        }

        public void Replace(string displayName, Type srcActivityType, Type targetActivityType)
        {
            var foundActivity = this.myActivityImplementations.Find(x => x.GetType() == srcActivityType) as Activity;
            if (foundActivity != null)
            {
                this.ReplaceInImplementationList(displayName, srcActivityType, targetActivityType);
            }

            foreach (var activityImplementation in this.myActivityImplementations)
            {
                this.FindAndReplace(activityImplementation, displayName, srcActivityType, targetActivityType);
            }
        }

        private void ReplaceInImplementationList(string displayName, Type srcActivityType, Type targetActivityType)
        {
            var foundActivity = this.myActivityImplementations.Find(x => x.GetType() == srcActivityType && this.CheckForDisplayName(displayName, x)) as Activity;
            if (this.myIsUsingActivityImplementation)
            {
                var mockActivity = this.CreateMockActivity(foundActivity, targetActivityType);
                this.myActivityImplementations.Clear();
                this.myActivityImplementations.Add(mockActivity);
            }
            else if (this.myIsUsingCollection)
            {
                this.myActivityImplementations.Clear();
                var activityCollectionProperties = this.myActivty.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(this.CheckForActivityCollection).ToArray();
                var listOfActivities = new List<object>();
                foreach (var activityCollectionProperty in activityCollectionProperties)
                {
                    var collectionValue = activityCollectionProperty.GetValue(this.myActivty) as ICollection<Activity>;
                    var collectionObjectValue = activityCollectionProperty.GetValue(this.myActivty) as Collection<Activity>;
                    if (collectionObjectValue != null)
                    {
                        Activity foundValue = null;
                        do
                        {
                            foundValue = collectionObjectValue.SingleOrDefault(x => x.GetType() == srcActivityType && this.CheckForDisplayName(displayName, x));
                            if (foundValue != null)
                            {
                                var mockActivity = this.CreateMockActivity(foundValue, targetActivityType);
                                var foundIndex = collectionObjectValue.IndexOf(foundValue);
                                collectionObjectValue.RemoveAt(foundIndex);
                                collectionObjectValue.Insert(foundIndex, mockActivity);
                            }
                        }
                        while (foundValue != null);

                        listOfActivities.AddRange(collectionObjectValue);
                    }
                    else if (collectionValue != null)
                    {
                        Activity foundValue = null;
                        do
                        {
                            foundValue = collectionValue.SingleOrDefault(x => x.GetType() == srcActivityType && this.CheckForDisplayName(displayName, x));
                            if (foundValue != null)
                            {
                                var mockActivity = this.CreateMockActivity(foundValue, targetActivityType);
                                collectionValue.Remove(foundValue);
                                collectionValue.Add(mockActivity);
                            }
                        }
                        while (foundValue != null);
                        listOfActivities.AddRange(collectionValue);
                    }
                }

                this.myActivityImplementations.AddRange(listOfActivities);
            }
            else
            {
                this.myActivityImplementations.Clear();
                var fieldList = this.myActivty.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => info.FieldType.IsSubclassOf(typeof(Activity))).ToArray();
                foreach (var fieldInfo in fieldList)
                {
                    var fieldValue = fieldInfo.GetValue(this.myActivty);
                    if (fieldValue != null && fieldValue.GetType() == srcActivityType && this.CheckForDisplayName(displayName, fieldValue))
                    {
                        var mockActivity = this.CreateMockActivity(foundActivity, targetActivityType);
                        fieldInfo.SetValue(this.myActivty, mockActivity);
                        this.myActivityImplementations.Add(mockActivity);
                    }
                    this.myActivityImplementations.Add(fieldValue);
                }
            }
        }

        private void FindAndReplace(object parentObject, string displayName, Type srcActivityType, Type targetActivityType)
        {
            if (parentObject == null || parentObject.GetType() == targetActivityType)
            {
                return;
            }

            if (parentObject is Flowchart)
            {
                var flowNodeProperties = parentObject.GetType().GetProperties().Where(this.CheckForFlowNodeCollection).ToArray();
                foreach (var flowNodeProperty in flowNodeProperties)
                {
                    var flowNodeCollection = flowNodeProperty.GetValue(parentObject, null) as ICollection<FlowNode>;
                    if (flowNodeCollection != null)
                    {
                        foreach (var flowNode in flowNodeCollection)
                        {
                            this.FindAndReplace(flowNode, displayName, srcActivityType, targetActivityType);       
                        }
                    }
                }
            }

            PropertyInfo foundActivityProperty = null;
            do
            {
                var activityProperties = parentObject.GetType().GetProperties().Where(info => info.PropertyType.IsSubclassOf(typeof(Activity)) 
                                                                                           || info.PropertyType.IsAssignableFrom(typeof(Activity))
                                                                                           || info.PropertyType.IsAssignableFrom(typeof(PickBranch))
                                                                                           || info.PropertyType.IsSubclassOf(typeof(PickBranch))
                                                                                           || info.PropertyType.IsSubclassOf(typeof(ActivityDelegate))).ToArray();
                foundActivityProperty = activityProperties.SingleOrDefault(x =>
                                                                                { 
                                                                                    var propertyValue = x.GetValue(parentObject, null);
                                                                                    if (propertyValue != null && propertyValue.GetType() == srcActivityType && this.CheckForDisplayName(displayName, propertyValue))
                                                                                    {
                                                                                        return true;
                                                                                    }
                                                                                    return false;
                                                                                });
                if (foundActivityProperty != null)
                {
                    var foundActivty = foundActivityProperty.GetValue(parentObject, null) as Activity;
                    if (foundActivty != null)
                    {
                        var mockActivity = this.CreateMockActivity(foundActivty, targetActivityType);
                        foundActivityProperty.SetValue(parentObject, mockActivity, null);
                    }
                }
            }
            while (foundActivityProperty != null);

            var activityPropertyList = parentObject.GetType().GetProperties().Where(info => info.PropertyType.IsSubclassOf(typeof(Activity)) 
                                                                                         || info.PropertyType.IsAssignableFrom(typeof(Activity))
                                                                                         || info.PropertyType.IsAssignableFrom(typeof(PickBranch))
                                                                                         || info.PropertyType.IsSubclassOf(typeof(PickBranch))
                                                                                         || info.PropertyType.IsSubclassOf(typeof(ActivityDelegate))).ToArray();
            foreach (var propertyInfo in activityPropertyList)
            {
                var propertyValue = propertyInfo.GetValue(parentObject, null);
                if (propertyValue == null || propertyValue.GetType() == targetActivityType)
                {
                    continue;
                }
                
                this.FindAndReplace(propertyValue, displayName, srcActivityType, targetActivityType);
            }

            this.FindAndReplaceInDictionary(parentObject, displayName, srcActivityType, targetActivityType);

            var activityCollectionProperties = parentObject.GetType().GetProperties().Where(this.CheckForActivityCollection).ToArray();
            foreach (var activityCollectionProperty in activityCollectionProperties)
            {
                var collectionActivity = activityCollectionProperty.GetValue(parentObject, null) as Collection<Activity>;
                if (collectionActivity == null)
                {
                    continue;
                }

                Activity foundActivity = null;
                do
                {
                    foundActivity = collectionActivity.SingleOrDefault(x => x.GetType() == srcActivityType && this.CheckForDisplayName(displayName, x));
                    if (foundActivity != null)
                    {
                        var mockActivity = this.CreateMockActivity(foundActivity, targetActivityType);
                        var foundIndex = collectionActivity.IndexOf(foundActivity);
                        collectionActivity.RemoveAt(foundIndex);
                        collectionActivity.Insert(foundIndex, mockActivity);
                    }
                }
                while (foundActivity != null);

                foreach (var activity in collectionActivity)
                {
                    if (activity.GetType() == targetActivityType)
                    {
                        continue;
                    }

                    this.FindAndReplace(activity, displayName, srcActivityType, targetActivityType);
                }
            }
        }

        private void FindAndReplaceInDictionary(object parentObject, string displayName, Type srcActivityType, Type targetActivityType)
        {
            var propertyInfos = parentObject.GetType().GetProperties().Where(this.CheckForDictionary).ToArray();
            foreach (var dictionaryProperty in propertyInfos)
            {
                var dictObject = dictionaryProperty.GetValue(parentObject, null);
                if(dictObject == null)
                {
                    continue;
                }

                var keysPropertyInfo = dictObject.GetType().GetProperty("Keys");
                var keysValue = keysPropertyInfo.GetValue(dictObject, null);
                var genericTypes = keysValue.GetType().GetGenericArguments();
                var keyCountObject = keysValue.GetType().GetProperty("Count").GetValue(keysValue, null);

                var dictItemObject = dictObject.GetType().GetProperty("Item");

                var keysCount = int.Parse(keyCountObject.ToString());
                for (int i = 0; i < keysCount; i++)
                {
                    var elementAtInfo = typeof (System.Linq.Enumerable).GetMethod("ElementAt").MakeGenericMethod(genericTypes[0]);
                    var keyObject = elementAtInfo.Invoke(keysValue, new object[] {keysValue, i});

                    var activityObject = dictItemObject.GetValue(dictObject, new object[] { keyObject });
                    if (activityObject == null)
                    {
                        continue;
                    }
                    if(activityObject.GetType() == srcActivityType && this.CheckForDisplayName(displayName, activityObject))
                    {
                        var mockActivity = this.CreateMockActivity(activityObject as Activity, targetActivityType);
                        dictItemObject.SetValue(dictObject, mockActivity, new object[] { keyObject });
                    }
                    else
                    {
                        this.FindAndReplace(activityObject, displayName, srcActivityType, targetActivityType);
                    }
                }
            }
        }

        private Activity CreateMockActivity(Activity srcActivity, Type targetType)
        {
            var targetActivity = this.CreateObject(targetType) as Activity;
            if (targetActivity == null)
            {
                throw new ArgumentException("Invalid Mock Activty type");
            }

            var arguments = srcActivity.GetType().GetProperties().Where(FindArguments).ToArray();

            foreach (var propertyInfo in arguments)
            {
                var srcVal = propertyInfo.GetValue(srcActivity, null);
                var targetProperty = targetActivity.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType);
                if (targetProperty == null)
                {
                    throw new ArgumentException(propertyInfo.Name + " property is not found in mock.");
                }
                targetProperty.SetValue(targetActivity, srcVal, null);
            }

            return targetActivity;
        }

        private bool CheckForDictionary(PropertyInfo propertyInfo)
        {
            var propertyType = propertyInfo.PropertyType;
            if (propertyType.IsGenericType)
            {
                var genericType = propertyType.GetGenericTypeDefinition();

                if (genericType.IsSubclassOf(typeof(IDictionary<,>)) || genericType.IsAssignableFrom(typeof(IDictionary<,>)))
                {
                    var genericArguments = propertyType.GetGenericArguments();
                    if (genericArguments.Count() != 2)
                    {
                        return false;
                    }
                    var collectionType = genericArguments[1];
                    if (collectionType.IsAssignableFrom(typeof(Activity)) || collectionType.IsSubclassOf(typeof(Activity)))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private bool CheckForActivityCollection(PropertyInfo propertyInfo)
        {
            var propertyType = propertyInfo.PropertyType;
            if (propertyType.IsGenericType)
            {
                var genericType = propertyType.GetGenericTypeDefinition();
                var filterCriteria = new TypeFilter((type, criteria) => type.ToString() == criteria.ToString());

                var collectionInterfaceList = genericType.FindInterfaces(filterCriteria, "System.Collections.ICollection");
                var enumerableInterfaceList = genericType.FindInterfaces(filterCriteria, "System.Collections.IEnumerable");
                var interfaceList = new List<Type>();
                interfaceList.AddRange(collectionInterfaceList);
                interfaceList.AddRange(enumerableInterfaceList);

                if (interfaceList.Count > 0)
                {
                    var genericArguments = propertyType.GetGenericArguments();
                    if (genericArguments.Count() != 1)
                    {
                        return false;
                    }
                    var collectionType = genericArguments[0];
                    if (collectionType.IsAssignableFrom(typeof(Activity)) || collectionType.IsSubclassOf(typeof(Activity)))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private bool CheckForActivityCollection(FieldInfo fieldInfo)
        {
            var propertyType = fieldInfo.FieldType;
            if (propertyType.IsGenericType)
            {
                var genericType = propertyType.GetGenericTypeDefinition();
                var filterCriteria = new TypeFilter((type, criteria) => type.ToString() == criteria.ToString());
                var interfaceList = genericType.FindInterfaces(filterCriteria, "System.Collections.ICollection");
                if (interfaceList.Length > 0)
                {
                    var genericArguments = propertyType.GetGenericArguments();
                    if (genericArguments.Count() != 1)
                    {
                        return false;
                    }
                    var collectionType = genericArguments[0];
                    if (collectionType.IsAssignableFrom(typeof(Activity)) || collectionType.IsSubclassOf(typeof(Activity)))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private bool CheckForFlowNodeCollection(PropertyInfo propertyInfo)
        {
            var propertyType = propertyInfo.PropertyType;
            if (propertyType.IsGenericType)
            {
                var genericType = propertyType.GetGenericTypeDefinition();
                var filterCriteria = new TypeFilter((type, criteria) => type.ToString() == criteria.ToString());

                var collectionInterfaceList = genericType.FindInterfaces(filterCriteria, "System.Collections.ICollection");
                var enumerableInterfaceList = genericType.FindInterfaces(filterCriteria, "System.Collections.IEnumerable");
                var interfaceList = new List<Type>();
                interfaceList.AddRange(collectionInterfaceList);
                interfaceList.AddRange(enumerableInterfaceList);

                if (interfaceList.Count > 0)
                {
                    var genericArguments = propertyType.GetGenericArguments();
                    if (genericArguments.Count() != 1)
                    {
                        return false;
                    }
                    var collectionType = genericArguments[0];
                    if (collectionType.IsAssignableFrom(typeof(FlowNode)) || collectionType.IsSubclassOf(typeof(FlowNode)))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private bool FindArguments(PropertyInfo propertyInfo)
        {
            var propertyType = propertyInfo.PropertyType;
            if (propertyType.IsGenericType)
            {
                var genericType = propertyType.GetGenericTypeDefinition();
                if (genericType.IsAssignableFrom(typeof(InArgument<>)) || genericType.IsAssignableFrom(typeof(OutArgument<>)) || genericType.IsAssignableFrom(typeof(InOutArgument<>)))
                {
                    return true;
                }
            }
            else
            {
                if (propertyType.IsAssignableFrom(typeof(Activity)) || propertyType.IsAssignableFrom(typeof(Collection<Activity>)))
                {
                    return true;
                }
            }

            return false;
        }

        private Activity GetActityImplementation(object parentObject)
        {
            var propertyInfo = parentObject.GetType().GetProperty("Implementation", BindingFlags.NonPublic | BindingFlags.Instance);
            var implDelegate = propertyInfo.GetValue(parentObject, null) as Func<Activity>;
            if (implDelegate != null)
            {
                return implDelegate();
            }

            return null;
        }

        private void SetActityImplementation(object parentObject)
        {
            if (this.myActivityImplementations == null || this.myActivityImplementations.Count == 0)
            {
                return;
            }

            var activityImplementation = this.myActivityImplementations[0] as Activity;
            if (activityImplementation != null)
            {
                Func<Activity> funcActivity = () => activityImplementation;
                var propertyInfo = parentObject.GetType().GetProperty("Implementation", BindingFlags.NonPublic | BindingFlags.Instance);
                propertyInfo.SetValue(parentObject, funcActivity, null);
            }
        }

        private List<object> FindImplementationFields(object parentObject)
        {
            var implementationChilds = new List<object>();

            var fieldList = parentObject.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => info.FieldType.IsSubclassOf(typeof(Activity)) || info.FieldType.IsAssignableFrom(typeof(Activity))).ToArray();
            if (fieldList.Length == 0)
            {
                var activityCollectionProperties = parentObject.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(this.CheckForActivityCollection).ToArray();
                var listOfActivities = new List<object>();
                foreach (var activityCollectionProperty in activityCollectionProperties)
                {
                    var collectionValue = activityCollectionProperty.GetValue(parentObject) as ICollection<Activity>;
                    if (collectionValue != null)
                    {
                        listOfActivities.AddRange(collectionValue);
                    }
                }
                if (listOfActivities.Count > 0)
                {
                    implementationChilds.AddRange(listOfActivities);
                    this.myIsUsingCollection = true;
                }
                else
                {
                    var implementationChild = this.GetActityImplementation(parentObject);
                    if (implementationChild != null)
                    {
                        myIsUsingActivityImplementation = true;
                        implementationChilds.Add(implementationChild);
                    }
                }
            }
            else
            {
                implementationChilds.AddRange(fieldList.Select(fieldInfo => fieldInfo.GetValue(parentObject)));
            }

            return implementationChilds;
        }

        public Activity GetActivity()
        {
            if (myIsUsingActivityImplementation)
            {
                this.SetActityImplementation(this.myActivty);
            }
            return this.myActivty as Activity;
        }

        public void Replace(Type srcActivityType, IMockBehaviour mockBehaviour)
        {
            var foundActivity = this.myActivityImplementations.Find(x => x.GetType() == srcActivityType) as Activity;
            if (foundActivity != null)
            {
                this.ReplaceInImplementationList(null, srcActivityType, mockBehaviour);
            }

            foreach (var activityImplementation in this.myActivityImplementations)
            {
                this.FindAndReplace(activityImplementation, null, srcActivityType, mockBehaviour);
            }
        }

        public void Replace(string displayName, Type srcActivityType, IMockBehaviour mockBehaviour)
        {
            var foundActivity = this.myActivityImplementations.Find(x => x.GetType() == srcActivityType) as Activity;
            if (foundActivity != null)
            {
                this.ReplaceInImplementationList(displayName, srcActivityType, mockBehaviour);
            }

            foreach (var activityImplementation in this.myActivityImplementations)
            {
                this.FindAndReplace(activityImplementation, displayName, srcActivityType, mockBehaviour);
            }
        }

        private void ReplaceInImplementationList(string displayName, Type srcActivityType, IMockBehaviour mockBehaviour)
        {
            var foundActivity = this.myActivityImplementations.Find(x => x.GetType() == srcActivityType && this.CheckForDisplayName(displayName, x)) as Activity;
            if (this.myIsUsingActivityImplementation)
            {
                var mockActivity = this.CreateMockActivity(foundActivity, mockBehaviour);
                this.myActivityImplementations.Clear();
                this.myActivityImplementations.Add(mockActivity);
            }
            else if (this.myIsUsingCollection)
            {
                this.myActivityImplementations.Clear();
                var activityCollectionProperties = this.myActivty.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(this.CheckForActivityCollection).ToArray();
                var listOfActivities = new List<object>();
                foreach (var activityCollectionProperty in activityCollectionProperties)
                {
                    var collectionValue = activityCollectionProperty.GetValue(this.myActivty) as ICollection<Activity>;
                    var collectionObjectValue = activityCollectionProperty.GetValue(this.myActivty) as Collection<Activity>;
                    if (collectionObjectValue != null)
                    {
                        Activity foundValue = null;
                        do
                        {
                            foundValue = collectionObjectValue.SingleOrDefault(x => x.GetType() == srcActivityType && this.CheckForDisplayName(displayName, x));
                            if (foundValue != null)
                            {
                                var mockActivity = this.CreateMockActivity(foundValue, mockBehaviour);
                                var foundIndex = collectionObjectValue.IndexOf(foundValue);
                                collectionObjectValue.RemoveAt(foundIndex);
                                collectionObjectValue.Insert(foundIndex, mockActivity);
                            }
                        }
                        while (foundValue != null);

                        listOfActivities.AddRange(collectionObjectValue);
                    }
                    else if (collectionValue != null)
                    {
                        Activity foundValue = null;
                        do
                        {
                            foundValue = collectionValue.SingleOrDefault(x => x.GetType() == srcActivityType && this.CheckForDisplayName(displayName, x));
                            if (foundValue != null)
                            {
                                var mockActivity = this.CreateMockActivity(foundValue, mockBehaviour);
                                collectionValue.Remove(foundValue);
                                collectionValue.Add(mockActivity);
                            }
                        }
                        while (foundValue != null);
                        listOfActivities.AddRange(collectionValue);
                    }
                }

                this.myActivityImplementations.AddRange(listOfActivities);
            }
            else
            {
                this.myActivityImplementations.Clear();
                var fieldList = this.myActivty.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => info.FieldType.IsSubclassOf(typeof(Activity))
                                                                                                                              || info.FieldType.IsAssignableFrom(typeof(Activity))).ToArray();
                foreach (var fieldInfo in fieldList)
                {
                    var fieldValue = fieldInfo.GetValue(this.myActivty);
                    if (fieldValue != null && fieldValue.GetType() == srcActivityType && this.CheckForDisplayName(displayName, fieldValue))
                    {
                        var mockActivity = this.CreateMockActivity(fieldValue as Activity, mockBehaviour);
                        fieldInfo.SetValue(this.myActivty, mockActivity);
                        this.myActivityImplementations.Add(mockActivity);
                    }
                    this.myActivityImplementations.Add(fieldValue);
                }
            }
        }

        private void FindAndReplace(object parentObject, string displayName, Type srcActivityType, IMockBehaviour mockBehaviour)
        {
            if (parentObject == null || parentObject is MockActivity)
            {
                return;
            }

            if (parentObject is Flowchart)
            {
                var flowNodeProperties = parentObject.GetType().GetProperties().Where(this.CheckForFlowNodeCollection).ToArray();
                foreach (var flowNodeProperty in flowNodeProperties)
                {
                    var flowNodeCollection = flowNodeProperty.GetValue(parentObject, null) as ICollection<FlowNode>;
                    if (flowNodeCollection != null)
                    {
                        foreach (var flowNode in flowNodeCollection)
                        {
                            this.FindAndReplace(flowNode, displayName, srcActivityType, mockBehaviour);
                        }
                    }
                }
            }

            PropertyInfo foundActivityProperty = null;
            do
            {
                var activityProperties = parentObject.GetType().GetProperties().Where(info => info.PropertyType.IsSubclassOf(typeof(Activity))
                                                                                           || info.PropertyType.IsAssignableFrom(typeof(Activity))
                                                                                           || info.PropertyType.IsAssignableFrom(typeof(PickBranch))
                                                                                           || info.PropertyType.IsSubclassOf(typeof(PickBranch))
                                                                                           || info.PropertyType.IsSubclassOf(typeof(ActivityDelegate))).ToArray();
                foundActivityProperty = activityProperties.SingleOrDefault(x =>
                                                                            {
                                                                                var propertyValue = x.GetValue(parentObject, null);
                                                                                if (propertyValue != null && propertyValue.GetType() == srcActivityType && this.CheckForDisplayName(displayName, propertyValue))
                                                                                {
                                                                                    return true;
                                                                                }
                                                                                return false;
                                                                            });
                if (foundActivityProperty != null)
                {
                    var foundActivty = foundActivityProperty.GetValue(parentObject, null) as Activity;
                    if (foundActivty != null)
                    {
                        var mockActivity = this.CreateMockActivity(foundActivty, mockBehaviour);
                        foundActivityProperty.SetValue(parentObject, mockActivity, null);
                    }
                }
            }
            while (foundActivityProperty != null);

            var activityPropertyList = parentObject.GetType().GetProperties().Where(info => info.PropertyType.IsSubclassOf(typeof(Activity))
                                                                                         || info.PropertyType.IsAssignableFrom(typeof(Activity))
                                                                                         || info.PropertyType.IsAssignableFrom(typeof(PickBranch))
                                                                                         || info.PropertyType.IsSubclassOf(typeof(PickBranch))
                                                                                         || info.PropertyType.IsSubclassOf(typeof(ActivityDelegate))).ToArray();
            foreach (var propertyInfo in activityPropertyList)
            {
                var propertyValue = propertyInfo.GetValue(parentObject, null);
                if (propertyValue == null || propertyValue is MockActivity)
                {
                    continue;
                }

                this.FindAndReplace(propertyValue, displayName, srcActivityType, mockBehaviour);
            }

            this.FindAndReplaceInDictionary(parentObject, displayName, srcActivityType, mockBehaviour);

            var activityCollectionProperties = parentObject.GetType().GetProperties().Where(this.CheckForActivityCollection).ToArray();
            foreach (var activityCollectionProperty in activityCollectionProperties)
            {
                var collectionActivity = activityCollectionProperty.GetValue(parentObject, null) as Collection<Activity>;
                if (collectionActivity == null)
                {
                    continue;
                }

                Activity foundActivity = null;
                do
                {
                    foundActivity = collectionActivity.SingleOrDefault(x => x.GetType() == srcActivityType && this.CheckForDisplayName(displayName, x));
                    if (foundActivity != null)
                    {
                        var mockActivity = this.CreateMockActivity(foundActivity, mockBehaviour);
                        var foundIndex = collectionActivity.IndexOf(foundActivity);
                        collectionActivity.RemoveAt(foundIndex);
                        collectionActivity.Insert(foundIndex, mockActivity);
                    }
                }
                while (foundActivity != null);

                foreach (var activity in collectionActivity)
                {
                    if (activity is MockActivity)
                    {
                        continue;
                    }

                    this.FindAndReplace(activity, displayName, srcActivityType, mockBehaviour);
                }
            }
        }

        private void FindAndReplaceInDictionary(object parentObject, string displayName, Type srcActivityType, IMockBehaviour mockBehaviour)
        {
            var propertyInfos = parentObject.GetType().GetProperties().Where(this.CheckForDictionary).ToArray();
            foreach (var dictionaryProperty in propertyInfos)
            {
                var dictObject = dictionaryProperty.GetValue(parentObject, null);
                if (dictObject == null)
                {
                    continue;
                }

                var keysPropertyInfo = dictObject.GetType().GetProperty("Keys");
                var keysValue = keysPropertyInfo.GetValue(dictObject, null);
                var genericTypes = keysValue.GetType().GetGenericArguments();
                var keyCountObject = keysValue.GetType().GetProperty("Count").GetValue(keysValue, null);

                var dictItemObject = dictObject.GetType().GetProperty("Item");

                var keysCount = int.Parse(keyCountObject.ToString());
                for (int i = 0; i < keysCount; i++)
                {
                    var elementAtInfo = typeof(System.Linq.Enumerable).GetMethod("ElementAt").MakeGenericMethod(genericTypes[0]);
                    var keyObject = elementAtInfo.Invoke(keysValue, new object[] { keysValue, i });

                    var activityObject = dictItemObject.GetValue(dictObject, new object[] { keyObject });
                    if (activityObject == null)
                    {
                        continue;
                    }
                    if (activityObject.GetType() == srcActivityType && this.CheckForDisplayName(displayName, activityObject))
                    {
                        var mockActivity = this.CreateMockActivity(activityObject as Activity, mockBehaviour);
                        dictItemObject.SetValue(dictObject, mockActivity, new object[] { keyObject });
                    }
                    else
                    {
                        this.FindAndReplace(activityObject, displayName, srcActivityType, mockBehaviour);
                    }
                }
            }
        }

        private Activity CreateMockActivity(Activity srcActivity, IMockBehaviour mockBehaviour)
        {
            var targetActivity = new MockActivity
                                     {
                                         MockBehaviour = new InArgument<IMockBehaviour>(cxt => mockBehaviour)
                                     };
            var mockArguments = new Dictionary<string, Argument>();
            var arguments = srcActivity.GetType().GetProperties().Where(FindArguments).ToArray();

            foreach (var propertyInfo in arguments)
            {
                var srcVal = propertyInfo.GetValue(srcActivity, null);
                mockArguments.Add(propertyInfo.Name, srcVal as Argument);
            }

            targetActivity.Arguments = mockArguments;

            return targetActivity;
        }

        private bool CheckForDisplayName(string displayname, object activityObject)
        {
            if(string.IsNullOrEmpty(displayname))
            {
                return true;
            }
            var actObject = activityObject as Activity;
            if (actObject != null && actObject.DisplayName == displayname)
            {
                return true;
            }

            return false;
        }
    }

    public sealed class MockActivity : CodeActivity
    {
        public Dictionary<string, Argument> Arguments { get; set; }

        public InArgument<IMockBehaviour> MockBehaviour { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            var mockBehaviour = this.MockBehaviour.Get(context);
            if(mockBehaviour == null)
            {
                return;
            }

            var inputArguments = Arguments.Where(argument => argument.Value is InArgument || argument.Value is InOutArgument).ToDictionary(argument => argument.Key, argument => argument.Value.Get(context));
            var outputArguments = mockBehaviour.Behaviour(inputArguments);
            if(outputArguments != null)
            {
                foreach (var outputArgument in outputArguments)
                {
                    var argument = this.Arguments[outputArgument.Key];
                    argument.Set(context, outputArguments[outputArgument.Key]);
                }
            }
        }
    }

    public interface IMockBehaviour
    {
        Dictionary<string, object> Behaviour(Dictionary<string, object> input);
    }
}
